/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const path = require('path');
const fs = require('fs');
const ts = require('typescript');
const ExcelJS = require('exceljs');
let inputDir = '';
let fileNeedChange = false;
let currentFileContent = '';
let currentFilePath = '';
const apiInfos = Array();
const API_TYPE = new Map([
  [ts.SyntaxKind.ClassDeclaration, 'class'],
  [ts.SyntaxKind.StructDeclaration, 'struct'],
  [ts.SyntaxKind.InterfaceDeclaration, 'interface'],
  [ts.SyntaxKind.EnumDeclaration, 'enum_class'],
  [ts.SyntaxKind.EnumMember, 'enum_instance'],
  [ts.SyntaxKind.ModuleDeclaration, 'namespace'],
  [ts.SyntaxKind.MethodDeclaration, 'method'],
  [ts.SyntaxKind.MethodSignature, 'method'],
  [ts.SyntaxKind.FunctionDeclaration, 'method'],
  [ts.SyntaxKind.Constructor, 'method'],
  [ts.SyntaxKind.ConstructSignature, 'method'],
  [ts.SyntaxKind.CallSignature, 'method'],
  [ts.SyntaxKind.GetAccessor, 'method'],
  [ts.SyntaxKind.SetAccessor, 'method'],
  [ts.SyntaxKind.IndexSignature, 'method'],
  [ts.SyntaxKind.PropertyDeclaration, 'field'],
  [ts.SyntaxKind.PropertySignature, 'field'],
  [ts.SyntaxKind.VariableStatement, 'field'],
  [ts.SyntaxKind.AnnotationPropertyDeclaration, 'field'],
  [ts.SyntaxKind.TypeAliasDeclaration, 'field'],
  [ts.SyntaxKind.ExportAssignment, 'export'],
  [ts.SyntaxKind.ExportDeclaration, 'export'],
  [ts.SyntaxKind.OverloadDeclaration, 'overload'],
  [ts.SyntaxKind.AnnotationDeclaration, 'annotatiom'],
]);

const COMPILER_OPTIONS = {
  target: ts.ScriptTarget.ES2017,
  etsAnnotationsEnable: true,
};

collectDeclaration(process.argv[2]);

/**
 * 脚本入口
 *
 * @param {string} interfacePath
 */
function collectDeclaration(interfacePath) {
  inputDir = interfacePath;
  // 入口
  try {
    const utFiles = [];
    const apiPath = path.resolve(inputDir, 'api');
    const arktsPath = path.resolve(inputDir, 'arkts');
    readFile(apiPath, utFiles); // 读取文件
    readFile(arktsPath, utFiles); // 读取文件
    tsTransform(utFiles, transformNode);
    exportApiInfos();
  } catch (error) {
    console.error('DELETE_SYSTEM_PLUGIN ERROR: ', error);
  }
}

/**
 * 导出文件
 */
async function exportApiInfos() {
  const workbook = new ExcelJS.Workbook();
  const sheet = workbook.addWorksheet('api差异', { viers: [{ xSplit: 1 }] });
  sheet.getRow(1).values = [
    'apiId',
    'apiName',
    'apiType',
    'filePath',
    'parentName',
    'apiDefine',
    'staticVersion',
    'note',
    'newNote',
  ];
  for (let i = 0; i < apiInfos.length; i++) {
    let apiData = apiInfos[i];
    const filePath = '/interface/sdk-js/' + path.relative(inputDir, apiData.filePath).replace(/\\/g, '/');
    let apiId =
      apiData.apiName +
      '#' +
      apiData.apiType +
      '#' +
      filePath +
      '#' +
      apiData.parentName +
      '#' +
      apiData.apiDefine +
      '#' +
      apiData.staticVersion;
    const ADD_NUMBER = 2;
    sheet.getRow(i + ADD_NUMBER).values = [
      apiId,
      apiData.apiName,
      apiData.apiType,
      filePath,
      apiData.parentName,
      apiData.apiDefine,
      apiData.staticVersion,
      apiData.note,
      apiData.newNote,
    ];
  }
  const buffer = await workbook.xlsx.writeBuffer();
  fs.writeFile('./change_since.xlsx', buffer, function (err) {
    if (err) {
      return;
    }
  });
}

/**
 * 统一处理文件名称，修改后缀等
 * @param {string} filePath 文件路径
 * @returns {string} filename 文件名称
 */
function processFileName(filePath) {
  return path
    .basename(filePath)
    .replace(/\.d\.ts$/g, '.ts')
    .replace(/\.d\.ets$/g, '.ets');
}

/**
 * 遍历所有文件进行处理
 * @param {Array} utFiles 所有文件
 * @param {transformNode} callback 回调函数
 */
function tsTransform(utFiles, callback) {
  utFiles.forEach(url => {
    if (!fs.existsSync(url)) {
      return;
    }
    const apiBaseName = path.basename(url);
    currentFileContent = fs.readFileSync(url, 'utf-8'); // 文件内容
    currentFilePath = url;
    fileNeedChange = false;
    let isTransformer = /\.d\.ts/.test(apiBaseName) || /\.d\.ets/.test(apiBaseName);
    if (/\.json/.test(url) || apiBaseName === 'index-full.d.ts') {
      isTransformer = false;
    }

    if (!isTransformer) {
      // writeFile(url, content);
      return;
    }
    // dts文件处理
    const fileName = processFileName(url);
    ts.transpileModule(currentFileContent, {
      compilerOptions: COMPILER_OPTIONS,
      fileName: fileName,
      transformers: { before: [callback(url)] },
    });
  });
}

/**
 * 读取目录下所有文件
 * @param {string} dir 文件目录
 * @param {Array} utFiles 所有文件
 */
function readFile(dir, utFiles) {
  try {
    const files = fs.readdirSync(dir);
    files.forEach(element => {
      const filePath = path.join(dir, element);
      const status = fs.statSync(filePath);
      if (status.isDirectory()) {
        readFile(filePath, utFiles);
      } else {
        utFiles.push(filePath);
      }
    });
  } catch (e) {
    console.log('ETS ERROR: ' + e);
  }
}

/**
 * 每个文件处理前回调函数
 * @callback transformNode
 * @param {string} url 文件路径
 * @returns {Function}
 */
function transformNode(url) {
  return context => {
    return node => {
      sourceFile = node;
      node = processVisitEachChild(context, node);
      const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
      // const result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
      if (fileNeedChange) {
        writeFile(url, currentFileContent);
      }
      // const fileName = processFileName(url);
      return ts.factory.createSourceFile([], ts.SyntaxKind.EndOfFileToken, ts.NodeFlags.None);
    };
  };
}

/**
 * 输出文件
 *
 * @param {string} url 输入文件路径
 * @param {string} data 文件内容
 * @param {string | NodeJS.ArrayBufferView} option
 */
function writeFile(url, data, option) {
  const newFilePath = url;
  fs.mkdir(path.dirname(newFilePath), { recursive: true }, err => {
    if (err) {
      console.log(`ERROR FOR CREATE PATH ${err}`);
    } else {
      if (data === '') {
        fs.rmSync(newFilePath, { force: true });
        return;
      }
      fs.writeFileSync(newFilePath, data, option, err => {
        if (err) {
          console.log(`ERROR FOR CREATE FILE ${err}`);
        }
      });
    }
  });
}

/**
 * 遍历节点
 *
 * @param {ts.TransformationContext} context
 * @param {ts.Node} node
 * @returns
 */
/**
 * 遍历节点（带全量调试信息，处理多段JSDoc）
 * @param {ts.TransformationContext} context
 * @param {ts.Node} node
 * @returns {ts.Node}
 */
/**
 * 遍历节点（仅多段JSDoc时输出调试信息，处理多段JSDoc）
 * @param {ts.TransformationContext} context
 * @param {ts.Node} node
 * @returns {ts.Node}
 */
/**
 * 遍历节点（仅打印ohos.accessibility.d.ts的多段JSDoc调试信息）
 * @param {ts.TransformationContext} context
 * @param {ts.Node} node
 * @returns {ts.Node}
 */
/**
 * 遍历节点（仅多段JSDoc时输出调试信息，处理多段JSDoc）
 * @param {ts.TransformationContext} context
 * @param {ts.Node} node
 * @returns {ts.Node}
 */
/**
 * 遍历节点（修复缩进差异导致的替换失效问题）
 * @param {ts.TransformationContext} context
 * @param {ts.Node} node
 * @returns {ts.Node}
 */
function processVisitEachChild(context, node) {
  const DEBUG_TAG = "[多段JSDoc调试]";
  const TARGET_FILE_NAME = "accessibility.ts";
  let isDebug = false;

  return ts.visitEachChild(node, processAllNodes, context);

  function processAllNodes(node) {
    if (!node.jsDoc) {
      return ts.visitEachChild(node, processAllNodes, context);
    }

    // 1. 兼容函数/属性等所有节点类型 + 目标文件过滤
    const sourceFile = node.getSourceFile();
    const fileName = sourceFile?.fileName || "";
    const shortFileName = path.basename(fileName);
    const isTargetFile = shortFileName.includes(TARGET_FILE_NAME);
    const isMultiDoc = node.jsDoc.length > 1;
    isDebug = isTargetFile && isMultiDoc;

    // 2. 强制打印getTouchModeSync节点的信息（精准定位）
    const nodeName = node.name?.getText() || '匿名';
    if (isTargetFile && nodeName === 'getTouchModeSync') {
      console.log(`\n${DEBUG_TAG} === 发现目标函数：getTouchModeSync ===`);
      console.log(`${DEBUG_TAG} JSDoc段数：${node.jsDoc.length}`);
      console.log(`${DEBUG_TAG} 节点类型：${ts.SyntaxKind[node.kind]}`);
    }

    // 3. 最后一段JSDoc条件判断（保留原有逻辑）
    const isFinalDocMatch = hasFinalStaticJSDoc(node.jsDoc);

    if (isFinalDocMatch) {
      let fileChanged = false;
      let allJSDocsModified = [];
      let originalAllDocs = "";
      let modifiedAllDocs = "";

      node.jsDoc.forEach((jsDoc, index) => {
        const isLastDoc = index === node.jsDoc.length - 1;
        const docFullText = jsDoc.getFullText();
        originalAllDocs += docFullText;
        let modifiedDocText = docFullText;

        // 4. 修复核心：放宽前置段规则（匹配任意位置的@since 20）
        if (!isLastDoc) {
          // 强制打印getTouchModeSync的前置段信息
          if (nodeName === 'getTouchModeSync') {
            console.log(`${DEBUG_TAG} --- getTouchModeSync 第${index+1}段JSDoc ---`);
            console.log(`${DEBUG_TAG} 原始内容：\n${docFullText.trim()}`);
          }

          // 规则1：删除@since 20 static（兼容任意格式）
          const rule1Regex = /@since\s+20\s+static/g;
          if (rule1Regex.test(modifiedDocText)) {
            modifiedDocText = modifiedDocText.replace(rule1Regex, '');
            fileChanged = true;
            if (nodeName === 'getTouchModeSync') console.log(`${DEBUG_TAG} ✅ 删除了@since 20 static`);
          }

          // 规则2：替换@since 20 dynamic&static → @since 20 dynamic（兼容任意格式）
          const rule2Regex = /@since\s+20\s+dynamic&static/g;
          if (rule2Regex.test(modifiedDocText)) {
            modifiedDocText = modifiedDocText.replace(rule2Regex, '@since 20 dynamic');
            fileChanged = true;
            if (nodeName === 'getTouchModeSync') console.log(`${DEBUG_TAG} ✅ 替换了@since 20 dynamic&static`);
          }
        } else {
          // 最后一段处理（保留原有逻辑）
          modifiedDocText = modifiedDocText.replace(/(.*@since\s+)(\d+)(.*)/g, (substring, sinceStr, since, tag) => {
            const versionNum = Number(since);
            if (tag.includes('dynamic&static') && versionNum < 22) {
              fileChanged = true;
              return `${sinceStr}${since} dynamic\n${sinceStr}22 static`;
            } else if ((tag.includes('static') || tag.includes('staticonly')) && versionNum < 22) {
              fileChanged = true;
              return `${sinceStr}22 ${tag.replace(/^\s+/, '')}`;
            }
            return substring;
          });
        }

        allJSDocsModified.push(modifiedDocText);
        modifiedAllDocs += modifiedDocText;
      });

      // 5. 修复替换逻辑：直接全局替换文件中的目标文本（绕过AST拼接差异）
      if (fileChanged) {
        if (nodeName === 'getTouchModeSync') console.log(`${DEBUG_TAG} === 处理getTouchModeSync文件 ===`);

        // === 修复：整行替换 JSDoc 标签（删除整行，包括 * 前缀）===
        currentFileContent = currentFileContent
          .replace(/^(\s*\*)\s*@since\s+20\s+dynamic&static\s*\r?\n/gm, '$1 @since 20 dynamic\n')
          .replace(/^\s*\*\s*@since\s+20\s+static\s*\r?\n/gm, '');

        fileNeedChange = true;
        
        if (nodeName === 'getTouchModeSync') {
          console.log(`${DEBUG_TAG} ✅ getTouchModeSync 替换成功！`);
        }
      }
    }

    return ts.visitEachChild(node, processAllNodes, context);
  }
}

/**
 * 给基础apiInfo添加node相关信息
 *
 * @param {object} apiInfo
 * @param {ts.Node} node
 * @returns {object} apiInfo
 */
function setApiInfo(apiInfo, node) {
  apiInfo.apiType = API_TYPE.get(node.kind);
  apiInfo.apiName = getApiNodeName(node);
  // if (!apiInfo.apiType && !ts.isExpressionStatement(node)) {
  //   console.log(node.kind);
  // }
  apiInfo.filePath = currentFilePath;
  apiInfo.apiDefine = getApiDefine(node);
  apiInfo.parentName = getParentName(node);
  return apiInfo;
}

/**
 * 获取父节点信息
 *
 * @param {ts.Node} node
 * @returns {string}
 */
function getParentName(node) {
  let currentNode = node;
  let finalName = '';
  do {
    if (
      [
        ts.SyntaxKind.InterfaceDeclaration,
        ts.SyntaxKind.ClassDeclaration,
        ts.SyntaxKind.ModuleDeclaration,
        ts.SyntaxKind.EnumDeclaration,
        ts.SyntaxKind.StructDeclaration,
        ts.SyntaxKind.AnnotationDeclaration,
      ].includes(currentNode.kind)
    ) {
      finalName = '.' + getApiNodeName(currentNode) + finalName;
    }
    currentNode = currentNode.parent;
  } while (!ts.isSourceFile(currentNode));
  return finalName.replace(/^\./, '');
}

/**
 * 获取当前节点内容，只包含当前节点信息，不携带子节点内容
 *
 * @param {ts.Node} node
 * @returns {string} apiDefine
 */
function getApiDefine(node) {
  let apiDefine = node.getText();
  if (node.modifiers) {
    let isDefine = false;
    node.modifiers.forEach(item => {
      if (ts.isDecorator(item)) {
        if (!isDefine) {
          apiDefine = node.getFullText();
          isDefine = true;
        }
        apiDefine = apiDefine.substring(item.end - item.pos);
      }
    });
  }
  if (apiDefine.includes('{')) {
    const tagIndex = apiDefine.indexOf('{');
    apiDefine = apiDefine.substring(0, tagIndex);
  }
  apiDefine = apiDefine.replace(/(;|,)\s*$/g, '').trim();
  return apiDefine;
}

/**
 * 替换字符串最后一段匹配内容
 *
 * @param {string} str 待处理字符串
 * @param {string} search 查找内容
 * @param {string} replace 替换内容
 * @returns {string} str
 */
function replaceLastStr(str, search, replace) {
  const lastIndex = str.lastIndexOf(search);
  if (lastIndex === -1) {
    return str;
  }
  return str.substring(0, lastIndex) + replace + str.substring(lastIndex + search.length);
}

/**
 * 判断只有一段static相关since信息
 *
 * @param {ts.JSDoc[]} jsDocs jsdoc多段信息
 * @returns {boolean}
 */
function hasOnlyStaticJSDoc(jsDocs) {
  const staticJSdocs = [];
  for (let i = 0; i < jsDocs.length; i++) {
    const jsDoc = jsDocs[i];
    if (jsDoc.tags && hasStaticSinceTag(jsDoc.tags)) {
      staticJSdocs.push(jsDoc);
    }
  }
  return staticJSdocs.length === 1;
}

/**
 * 判断最后一段static相关since信息
 *
 * @param {ts.JSDoc[]} jsDocs jsdoc多段信息
 * @returns {boolean}
 */
function hasFinalStaticJSDoc(jsDocs) {
  const jsDoc = jsDocs[jsDocs.length - 1];
  if (jsDoc.tags) {
    return hasStaticSinceTag(jsDoc.tags);
  }
  return false;
}

/**
 * 判断tags中存在static相关since标签
 *
 * @param {ts.JSDoc.tags} tags
 * @returns {boolean}
 */
function hasStaticSinceTag(tags) {
  for (let i = 0; i < tags.length; i++) {
    const tag = tags[i];
    if (tag.tagName.escapedText === 'since' && (tag.comment.endsWith('static') || tag.comment.endsWith('staticonly'))) {
    //if (tag.tagName.escapedText === 'since' && tag.comment.endsWith('static')) {
      return true;
    }
  }
  return false;
}

/**
 * 获取api节点名称，VariableStatement需要特殊处理
 *
 * @param { ts.Node } node
 * @returns { string }
 */
function getApiNodeName(node) {
  let apiName = '';
  if (ts.isVariableStatement(node)) {
    apiName = variableStatementGetEscapedText(node);
  } else if (ts.isExportAssignment(node)) {
    apiName = node.expression.escapedText;
  } else if (ts.isExportDeclaration(node)) {
    apiName = 'export default';
  } else if (ts.isIndexSignatureDeclaration(node)) {
    apiName = node.type.getText();
  } else if (ts.isConstructorDeclaration(node) || ts.isConstructSignatureDeclaration(node)) {
    apiName = 'constructor';
  } else {
    apiName = node.name?.getText();
  }
  return apiName;
}

/**
 * 获取 variableStatement节点名称
 * @param {ts.Node} statement
 * @returns {string} name
 */
function variableStatementGetEscapedText(statement) {
  let name = '';
  if (
    statement &&
    statement.declarationList &&
    statement.declarationList.declarations &&
    statement.declarationList.declarations.length > 0 &&
    statement.declarationList.declarations[0].name &&
    statement.declarationList.declarations[0].name.escapedText
  ) {
    name = statement.declarationList.declarations[0].name.escapedText.toString();
  }
  return name;
}
